﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace IronLua.Metadata
{
    public struct Instruction
    {

        private readonly uint instr;

        public Instruction(uint data)
        {
            this.instr = data;
            var opcode = BitMasks.GetOpCode(data);

            switch (opcode)
            {
                case OpCodes.Move:
                case OpCodes.LoadKx:
                case OpCodes.LoadNil:
                case OpCodes.LoadBool:
                case OpCodes.GetUpVal:
                case OpCodes.SetUpVal:
                case OpCodes.GetTable:
                case OpCodes.SetTable:
                case OpCodes.Add:
                case OpCodes.Sub:
                case OpCodes.Mul:
                case OpCodes.Div:
                case OpCodes.Pow:
                case OpCodes.Unm:
                case OpCodes.Not:
                case OpCodes.Len:
                case OpCodes.Concat:
                case OpCodes.Call:
                case OpCodes.Return:
                case OpCodes.TailCall:
                case OpCodes.VarArgs:
                case OpCodes.Self:
                case OpCodes.Eq:
                case OpCodes.Lt:
                case OpCodes.Le:
                case OpCodes.Test:
                case OpCodes.TestSet:
                case OpCodes.TForLoop:
                case OpCodes.NewTable:
                case OpCodes.SetList:
                    this.OpCodeMode = OpCodeModes.Abc;
                    break;

                case OpCodes.LoadK:
                case OpCodes.GetTabUp:
                case OpCodes.SetTabUp:
                case OpCodes.Closure:
                    this.OpCodeMode = OpCodeModes.Abx;
                    break;

                case OpCodes.Jmp:
                case OpCodes.ForPrep:
                case OpCodes.ForLoop:
                    this.OpCodeMode = OpCodeModes.Asbx;
                    break;

                case OpCodes.ExtraArg:
                    this.OpCodeMode = OpCodeModes.Ax;
                    break;

                default:
                    throw new InvalidDataException("Bad opcode.");
            }

        }       

        public readonly OpCodeModes     OpCodeMode;
        
        public OpCodes   OpCode { get { return BitMasks.GetOpCode(this.instr); } }
        public uint     A { get { return BitMasks.GetRegisterA(this.instr); } }
        public uint     Ax { get { return BitMasks.GetRegisterAx(this.instr); } }
        public uint     B { get { return BitMasks.GetRegisterB(this.instr); } }
        public uint     C { get { return BitMasks.GetRegisterC(this.instr); } }
        public uint     Bx { get { return BitMasks.GetRegisterBx(this.instr); } }
        public int      sBx { get { return BitMasks.GetRegisterSbx(this.instr); } }

        public bool     IsBK { get { return BitMasks.IsBK(this.instr); } }
        public bool     IsCK { get { return BitMasks.IsCK(this.instr); } }
        public uint     BKIndex { get { return BitMasks.GetBKIndex(this.instr); } }
        public uint     CKIndex { get { return BitMasks.GetCKIndex(this.instr); } }

        public override string ToString()
        {
            switch (this.OpCodeMode)
            {
                case OpCodeModes.Abc:
                    return "{0} ({1}, {2}, {3})".FormatWith(this.OpCode, this.A, this.B, this.C);

                case OpCodeModes.Abx:
                    return "{0} ({1}, {2})".FormatWith(this.OpCode, this.A, this.Bx);

                case OpCodeModes.Asbx:
                    return "{0} ({1}, {2})".FormatWith(this.OpCode, this.A, this.sBx);

                case OpCodeModes.Ax:
                    return "{0}".FormatWith(this.Ax);

                default:
                    return "??? ({0})".FormatWith(this.OpCode, this.instr);
            }
        }

    }
}
